/*
 Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
 Original sources are available at www.latestbit.com

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 BigDecimal.hpp. Created on: 01.03.2010 16:35:39 
 */

#ifndef BIGDECIMAL_HPP_
#define BIGDECIMAL_HPP_

#include "cpputils/CppUtilsDefinitions.hpp"
#include "cpputils/SystemException.hpp"
#include "BigInteger.hpp"

namespace cpputils {
namespace exttypes {

class BigDecimal {
public:
	// Initialization

	inline BigDecimal() {
		mpq_init(nValue);
	}

	inline BigDecimal(const BigDecimal& src) {
		mpq_init(nValue);
		mpq_set(nValue, src.getValueInInternalFormat());
	}

	inline BigDecimal(const BigInteger& src) {
		mpq_init(nValue);
		mpq_set_z(nValue, src.getValueInInternalFormat());
	}

	inline BigDecimal(unsigned long nLongValue) {
		mpq_init(nValue);
		mpq_set_ui(nValue, nLongValue, 1 );
	}

	inline BigDecimal(long nLongValue) {
		mpq_init(nValue);
		mpq_set_si(nValue, nLongValue, 1 );
	}

	inline BigDecimal(const BigInteger& nNumerator, const BigInteger& nDenominator) {
		mpq_init(nValue);
		mpq_set_num(nValue, nNumerator.getValueInInternalFormat());
		mpq_set_den(nValue, nDenominator.getValueInInternalFormat());
	}

	inline BigDecimal(unsigned long nNumerator, unsigned long nDenominator) {
		mpq_init(nValue);
		mpq_set_ui(nValue, nNumerator, nDenominator );
	}

	inline BigDecimal(long nNumerator, unsigned long nDenominator) {
		mpq_init(nValue);
		mpq_set_si(nValue, nNumerator, nDenominator );
	}

	inline BigDecimal(double nDoubleValue) {
		mpq_init(nValue);
		mpq_set_d(nValue, nDoubleValue );
	}

	inline BigDecimal(const std::string& sValueInStrForm, bool bIsInRationalFormat = false, bool bNormalize=true) {
		if(bIsInRationalFormat) {
			mpq_init(nValue);
			if(mpq_set_str(nValue, sValueInStrForm.c_str(), 0 ) < 0) {
				mpq_clear(nValue);
				throw SystemException("Incorrect decimal format is specified: "+sValueInStrForm);
			}
			if(bNormalize)
				normalize();
		}
		else {
			setupFromDecimalFormatString(sValueInStrForm, true, bNormalize);
		}
	}

	inline ~BigDecimal() {
		mpq_clear(nValue);
	}

	const mpq_t& getValueInInternalFormat() const {
		return nValue;
	}

	mpq_ptr refValueInInternalFormat() {
		return nValue;
	}

	BigInteger numerator() const {
		return BigInteger(mpq_numref(nValue));
	}

	BigInteger denominator() const {
		return BigInteger(mpq_denref(nValue));
	}

	CPPU_DLLPRFX void normalize();

	// Assigments

	inline void operator = (const BigDecimal& src) {
		mpq_set(nValue, src.getValueInInternalFormat());
	}
	inline void operator = (const BigInteger& src) {
		mpq_set_z(nValue, src.getValueInInternalFormat());
	}

	inline void operator = (const mpq_t& nInternalFormat) {
		mpq_set(nValue, nInternalFormat);
	}

	inline void operator = (unsigned long nSrcValue) {
		mpq_set_ui(nValue, nSrcValue, 1);
	}

	inline void operator = (long nSrcValue) {
		mpq_set_si(nValue, nSrcValue, 1);
	}

	inline void operator = (double nSrcValue) {
		mpq_set_d(nValue, nSrcValue);
	}

	inline void operator = (const std::string& sStrForm) {
		//if(mpq_set_str(nValue, sStrForm.c_str(), 0) < 0)
		//	throw SystemException("Incorrect integer format is specified: "+sStrForm);
		setupFromDecimalFormatString(sStrForm, false, true);
	}

	// Conversions
	inline operator double () const {
		return mpq_get_d(nValue);
	}

	inline operator std::string() const {
		return toDecimalFormatString();
	}

	CPPU_DLLPRFX std::string toDecimalFormatString(unsigned int nMaxPrecision = 8) const;

	inline std::string toRationalFormatString(int nBase=10) const {
		char* pOutputStr = NULL;
		pOutputStr = mpq_get_str(pOutputStr, nBase, nValue);
		std::string sResult;
		if(pOutputStr)
			sResult = pOutputStr;
		free( pOutputStr );
		return sResult;
	}

	// Arithmetic

	// Unary +/-
	friend inline BigDecimal operator+(const BigDecimal& oSrc) {
		BigDecimal oResult;
		mpq_abs(oResult.nValue, oSrc.getValueInInternalFormat());
		return oResult;
	}

	friend inline BigDecimal operator-(const BigDecimal& oSrc) {
		BigDecimal oResult;
		mpq_neg(oResult.nValue, oSrc.getValueInInternalFormat());
		return oResult;
	}

	// Binary +, -, *, /

	friend inline BigDecimal operator+(const BigDecimal& oFirst, const BigDecimal& oSecond) {
		BigDecimal oResult;
		mpq_add(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond.getValueInInternalFormat());
		return oResult;
	}

	inline void operator+=(const BigDecimal& oSrc) {
		mpq_t nNewValue;
		mpq_init(nNewValue);
		mpq_add(nNewValue, nValue, oSrc.getValueInInternalFormat());
		mpq_set(nValue, nNewValue);
		mpq_clear(nNewValue);
	}

	friend inline BigDecimal operator-(const BigDecimal& oFirst, const BigDecimal& oSecond) {
		BigDecimal oResult;
		mpq_sub(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond.getValueInInternalFormat());
		return oResult;
	}

	inline void operator-=(const BigDecimal& oSrc) {
		mpq_t nNewValue;
		mpq_init(nNewValue);
		mpq_sub(nNewValue, nValue, oSrc.getValueInInternalFormat());
		mpq_set(nValue, nNewValue);
		mpq_clear(nNewValue);
	}

	friend inline BigDecimal operator*(const BigDecimal& oFirst, const BigDecimal& oSecond) {
		BigDecimal oResult;
		mpq_mul(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond.getValueInInternalFormat());
		return oResult;
	}

	inline void operator*=(const BigDecimal& oSrc) {
		mpq_t nNewValue;
		mpq_init(nNewValue);
		mpq_mul(nNewValue, nValue, oSrc.getValueInInternalFormat());
		mpq_set(nValue, nNewValue);
		mpq_clear(nNewValue);
	}

	friend inline BigDecimal operator/(const BigDecimal& oFirst, const BigDecimal& oSecond) {
		BigDecimal oResult;
		mpq_div(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond.getValueInInternalFormat());
		return oResult;
	}

	inline void operator/=(const BigDecimal& oSrc) {
		mpq_t nNewValue;
		mpq_init(nNewValue);
		mpq_div(nNewValue, nValue, oSrc.getValueInInternalFormat());
		mpq_set(nValue, nNewValue);
		mpq_clear(nNewValue);
	}

	// Comparations
	inline bool operator > (const BigDecimal &src) const {
		return mpq_cmp(nValue, src.getValueInInternalFormat()) > 0;
	}

	inline bool operator < (const BigDecimal &src) const {
		return mpq_cmp(nValue, src.getValueInInternalFormat()) < 0;
	}

	inline bool operator >= (const BigDecimal &src) const {
		return mpq_cmp(nValue, src.getValueInInternalFormat()) >= 0;
	}

	inline bool operator <= (const BigDecimal &src) const {
		return mpq_cmp(nValue, src.getValueInInternalFormat()) <= 0;
	}

	inline bool operator == (const BigDecimal &src) const {
		// mpq_equal is much faster than mpq_cmp
		return mpq_equal(nValue, src.getValueInInternalFormat()) != 0;
	}

	inline bool operator != (const BigDecimal &src) const {
		// mpq_equal is much faster than mpq_cmp
		return mpq_equal(nValue, src.getValueInInternalFormat()) == 0;
	}

	// Output
	friend inline std::istream& operator>>(std::istream &oStream, BigDecimal &src) {
		std::string sTempStringValue;
		oStream >> sTempStringValue;
		src = sTempStringValue;
		return oStream;
	}

	friend inline std::ostream& operator<<(std::ostream &oStream, const BigDecimal &src) {
		oStream << src.toDecimalFormatString();
		return oStream;
	}

	// Predefined consts
public:
	CPPU_DLLPRFX const static BigDecimal ZERO;
	CPPU_DLLPRFX const static BigDecimal HALF;
	CPPU_DLLPRFX const static BigDecimal ONE;
	CPPU_DLLPRFX const static BigDecimal ONE_AND_HALF;
	CPPU_DLLPRFX const static BigDecimal TWO;
	CPPU_DLLPRFX const static BigDecimal TEN;
	CPPU_DLLPRFX const static BigDecimal HUNDRED;

protected:
	CPPU_DLLPRFX void setupFromDecimalFormatString(const std::string& sInputString, bool bPreInit, bool bNormalize);

protected:
	mpq_t nValue;
};

}
}

#endif /* BIGDECIMAL_HPP_ */
